using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace Beaker.OpenCube.Render.Maths
{
	public struct Matrix4
	{
		private static readonly Matrix4 Zero = new Matrix4( new double[4, 4] );
		private static readonly Matrix4 Identity =
			new Matrix4(
				new double[,]
				{
					{ 1d, 0d, 0d, 0d },
					{ 0d, 1d, 0d, 0d },
					{ 0d, 0d, 1d, 0d },
					{ 0d, 0d, 0d, 1d }
				}
			);

		private readonly double[,] _values;

		public Matrix4( params double[] values )
		{
			if( values.Length != 16 )
				throw new ArgumentException( "values" );

			_values = new double[4, 4];
			for( int y = 0; y < 4; y++ )
				for( int x = 0; x < 4; x++ )
					_values[x, y] = values[y * 4 + x];
		}

		public Matrix4( IEnumerable<double> values )
		{
			double[] input = values.ToArray();
			if( input.Length != 16 )
				throw new ArgumentException( "values" );

			_values = new double[4, 4];
			for( int y = 0; y < 4; y++ )
				for( int x = 0; x < 4; x++ )
					_values[x, y] = input[y * 4 + x];
		}

		public Matrix4( double[,] values )
		{
			if( values.GetLength( 0 ) != 4 || values.GetLength( 1 ) != 4 )
				throw new ArgumentException( "values" );

			_values = new double[4, 4];
			Array.Copy( values, _values, 16 );
		}

		[Pure]
		public double this[int x, int y]
		{
			get { return _values[x, y]; }
		}

		public Matrix4 Translate( Vector3 vector )
		{
			return
				new Matrix4(
					new double[,]
					{
						{ 1d, 0d, 0d, vector.X },
						{ 0d, 1d, 0d, vector.Y },
						{ 0d, 0d, 1d, vector.Z },
						{ 0d, 0d, 0d, 1d }
					}
				);
		}

		public Matrix4 Scale( Vector3 vector )
		{
			return
				new Matrix4(
					new double[,]
					{
						{ vector.X, 0d, 0d, 0d },
						{ 0d, vector.Y, 0d, 0d },
						{ 0d, 0d, vector.Z, 0d },
						{ 0d, 0d, 0d, 1d }
					}
				);
		}

		public Matrix4 RotateX( double angle )
		{
			return
				new Matrix4(
					new double[,]
					{
						{ 1d, 0d, 0d, 0d },
						{ 0d, Math.Cos( angle ), -Math.Sin( angle ), 0 },
						{ 0d, Math.Sin( angle ), Math.Cos( angle ), 0 },
						{ 0d, 0d, 0d, 0d }
					}
				);
		}

		public Matrix4 RotateY( double angle )
		{
			return
				new Matrix4(
					new double[,]
					{
						{ Math.Cos( angle ), 0d, Math.Sin( angle ), 0d },
						{ 0d, 1d, 0d, 0d },
						{ -Math.Sin( angle ), 0d, Math.Cos( angle ), 0d },
						{ 0d, 0d, 0d, 1d }
					}
				);
		}

		public Matrix4 RotateZ( double angle )
		{
			return
				new Matrix4(
					new double[,]
					{
						{ Math.Cos( angle ), -Math.Sin( angle ), 0d, 0d },
						{ Math.Sin( angle ), Math.Cos( angle ), 0d, 0d },
						{ 0d, 0d, 1d, 0d },
						{ 0d, 0d, 0d, 1d }
					}
				);
		}

		#region Add

		public Matrix4 Add( Matrix4 other )
		{
			double[,] result = new double[4, 4];

			for( int x = 0; x < 4; x++ )
				for( int y = 0; y < 4; y++ )
					result[x, y] = _values[x, y] + other._values[x, y];

			return new Matrix4( result );
		}

		public static Matrix4 Add( Matrix4 left, Matrix4 right )
		{
			return left.Add( right );
		}

		public static Matrix4 operator +( Matrix4 left, Matrix4 right )
		{
			return left.Add( right );
		}

		#endregion
		#region Subtract

		public Matrix4 Subtract( Matrix4 other )
		{
			double[,] result = new double[4, 4];

			for( int x = 0; x < 4; x++ )
				for( int y = 0; y < 4; y++ )
					result[x, y] = _values[x, y] - other._values[x, y];

			return new Matrix4( result );
		}

		public static Matrix4 Subtract( Matrix4 left, Matrix4 right )
		{
			return left.Subtract( right );
		}

		public static Matrix4 operator -( Matrix4 left, Matrix4 right )
		{
			return left.Subtract( right );
		}

		#endregion
		#region Multiply

		public Matrix4 Multiply( double d )
		{
			double[,] result = new double[4, 4];

			for( int x = 0; x < 4; x++ )
				for( int y = 0; y < 4; y++ )
					result[x, y] = _values[x, y] * d;

			return new Matrix4( result );
		}

		public static Matrix4 Multiply( Matrix4 left, double right )
		{
			return left.Multiply( right );
		}

		public static Matrix4 operator *( Matrix4 left, double right )
		{
			return left.Multiply( right );
		}

		public Matrix4 Multiply( Matrix4 other )
		{
			double[,] result = new double[4, 4];

			for( int y = 0; y < 4; y++ )
			{
				for( int x = 0; x < 4; x++ )
				{
					double sum = 0.0;

					for( int j = 0; j < 4; j++ )
						sum += _values[x, j] * other._values[j, y];

					result[x, y] = sum;
				}
			}

			return new Matrix4( result );
		}

		public static Matrix4 Multiply( Matrix4 left, Matrix4 right )
		{
			return left.Multiply( right );
		}

		public static Matrix4 operator *( Matrix4 left, Matrix4 right )
		{
			return left.Multiply( right );
		}

		#endregion
		#region Division

		public Matrix4 Divide( double d )
		{
			double[,] result = new double[4, 4];
			double inv = 1d / d;

			for( int x = 0; x < 4; x++ )
				for( int y = 0; y < 4; y++ )
					result[x, y] = _values[x, y] * inv;

			return new Matrix4( result );
		}

		public static Matrix4 Divide( Matrix4 left, double right )
		{
			return left.Divide( right );
		}

		public static Matrix4 operator /( Matrix4 left, double right )
		{
			return left.Divide( right );
		}

		#endregion
	}
}